资源
正文
096 配置代理_方式一
Note
在前端开发中,从服务端获取数据的方式有多种常用的方法,主要包括以下几种:
- AJAX(Asynchronous JavaScript and XML)
- 原生 JavaScript XMLHttpRequest:使用
XMLHttpRequest
对象发起请求,处理服务器响应。
- jQuery AJAX:通过
$.ajax()
或 $.get()
等方法发起请求。
- Fetch API:基于 Promises 进行处理。
- Axios
- WebSocket
- Server-Sent Events (SSE)
- GraphQL
跨域请求(Cross-Origin Request)是指在浏览器中,网页从一个域(源)向另一个域发起请求的情况。由于浏览器的同源策略(Same-Origin Policy),不同域之间的请求默认是被禁止的,为了实现跨域请求,需要遵循一定的规则和技术。
解决方案有:
- CORS(跨源资源共享):前端后端都要配置
- JSONP(JSON with Padding):利用
<script>
标签没有跨域限制的特点,服务器返回一个包装的 JavaScript 函数
- 代理(Proxy):前端请求可以先发送到同源的代理服务器,再由代理服务器转发请求到目标服务器。这种方式可以解决跨域问题,但需要额外的服务器配置。
- iframe + postMessage
- WebSocket
从资料中开启一个服务器:
1 2
| D:\XXXS\Vue技术全家桶\尚硅谷Vue2教程(天禹老师主讲)\资料(含课件)\05_其他\test_proxy_server>node server1 服务器1启动成功了,请求学生信息地址为:http://localhost:5000/students
|
服务器代码如下,当请求到 http://localhost:5000/students
后,便会返回 JSON 字符串:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const express = require('express') const app = express()
app.use((request,response,next)=>{ console.log('有人请求服务器1了'); next() })
app.get('/students',(request,response)=>{ const students = [ {id:'001',name:'tom',age:18}, {id:'002',name:'jerry',age:19}, {id:'003',name:'tony',age:120}, ] response.send(students) })
app.listen(5000,(err)=>{ if(!err) console.log('服务器1启动成功了,请求学生信息地址为:http://localhost:5000/students'); })
|
跨域请求必须保持三个一致(同源策略):协议名、主机名、端口号。
目前:
源 |
服务端 |
前端 |
协议名 |
http |
http |
主机名 |
localhost |
localhost |
端口号 |
5000 |
8080 |
因此,我们需要配置一个代理服务器以实现跨域请求,vue 脚手架中提供了配置代理服务器的一个简便方法。
代理服务器(粉)端口号为 8080,从 5000 端口获取数据并转发回前端,就实现了跨域请求。
在前端项目中,配置 vue.config.js
:
1 2 3 4 5 6 7 8 9 10
| module.exports = { pages: { index: { entry: 'src/main.js', }, }, devServer: { proxy: 'http://localhost:5000', }, }
|
App.vue
下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <div id="root"> <button @click="getStudents">获取学生信息</button> </div> </template>
<script> import axios from 'axios'; export default { name: 'App', methods: { getStudents() { axios.get('http://localhost:8080/students') // 使用代理服务器后,修改端口号 .then(response => { console.log('请求成功了:', response.data); }) .catch(error => { console.log(error); }); } }, } </script>
|
097 配置代理_方式二
通过如下方式可以配置多个代理服务器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| module.exports = { pages: { index: { entry: 'src/main.js', }, }, devServer: { proxy: { '/Student': { target: 'http://localhost:5000', changeOrigin: true, pathRewrite: { '^/Student': '', }, }, '/Car': { target: 'http://localhost:5001', changeOrigin: true, pathRewrite: { '^/Car': '', }, }, } }, }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <template> <div id="root"> <button @click="getStudents">获取学生信息</button> <button @click="getCars">获取车辆信息</button> </div> </template>
<script> import axios from 'axios'; export default { name: 'App', methods: { getStudents() { axios.get('http://localhost:8080/Student/students') .then(response => { console.log('请求成功了:', response.data); }) .catch(error => { console.log(error); }); }, getCars() { axios.get('http://localhost:8080/Car/cars') .then(response => { console.log('请求成功了:', response.data); }) .catch(error => { console.log(error); }); } }, } </script>
|
098 github 案例_静态组件
构建项目如下:
由于 bootstrap.css
是第三方库,直接引入在 App.vue
中会因为缺失字体导致编译失败,改为引入到 index.html
中。
List.vue
和 Search.vue
由给的资源分析结构得到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="./bootstrap.css"> <link rel="stylesheet" href="./index.css"> </head> <body> <div id="app"> <div class="container"> <section class="jumbotron"> <h3 class="jumbotron-heading">Search Github Users</h3> <div> <input type="text" placeholder="enter the name you search"/> <button>Search</button> </div> </section> <div class="row"> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> <div class="card"> <a href="https://github.com/xxxxxx" target="_blank"> <img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/> </a> <p class="card-text">xxxxxx</p> </div> </div> </div> </div> </body> </html>
|
099 github 案例_列表展示
借助 github 官方提供的 API:https://api.github.com/search/users?q=XXX,通过 get 请求可以查找 github 的用户,返回一串 JSON。基于此,在 Search.vue
接收数据,并通过数据总线将数据传递给 List.vue
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <template> <section class="jumbotron"> <h3 class="jumbotron-heading">Search Github Users</h3> <div> <input type="text" placeholder="enter the name you search" v-model="keyword" /> <button @click="searchUsers">Search</button> </div> </section> </template>
<script> /* eslint-disable vue/multi-word-component-names */ import axios from 'axios'; export default { name: 'Test', data() { return { keyword: "" } }, methods: { searchUsers() { axios.get(`https://api.github.com/search/users?q=${this.keyword}`) .then((response) => { console.log(response.data); this.$bus.$emit('gerUsers', response.data.items); }) .catch((error) => { console.log(error); }); } } } </script>
<style></style>
|
分析收到的 JSON 结构,在 List.vue
中接收并展示数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| <template> <div class="row"> <div class="card" v-for="user in users" :key="user.login"> <a :href="user.html_url" target="_blank"> <img :src="user.avatar_url" style='width: 100px'/> </a> <p class="card-text">{{user.login}}</p> </div> </div> </template>
<script> /* eslint-disable vue/multi-word-component-names */ export default { name: 'Test', data() { return { users: [] } }, mounted() { this.$bus.$on('gerUsers', (users) => { console.log('我是 List 组件,收到数据:', users); this.users = users; }) } } </script>
<style lnag="css" scoped> .album { min-height: 50rem; /* Can be removed; just added for demo purposes */ padding-top: 3rem; padding-bottom: 3rem; background-color: #f7f7f7; }
.card { float: left; width: 33.333%; padding: .75rem; margin-bottom: 2rem; border: 1px solid #efefef; text-align: center; }
.card>img { margin-bottom: .75rem; border-radius: 100px; }
.card-text { font-size: 85%; } </style>
|
100 github 案例_完善案例
在之前的案例上增加搜索进度的判断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| <template> <div> <!-- 展示用户列表--> <div class="row" v-if="info.users.length"> <div class="card" v-for="user in info.users" :key="user.login"> <a :href="user.html_url" target="_blank"> <img :src="user.avatar_url" style='width: 100px' /> </a> <p class="card-text">{{ user.login }}</p> </div> </div> <!-- 展示欢迎词 --> <h1 v-show="info.isFirst">欢迎使用!</h1> <!-- 展示加载中 --> <div v-show="info.isLoading">加载中...</div> <!-- 展示错误信息 --> <div v-show="info.errMsg">{{ info.errMsg }}</div> </div> </template>
<script> /* eslint-disable vue/multi-word-component-names */ export default { name: 'List', data() { return { info: { isFirst: true, isLoading: false, errMsg: '', users: [] } } }, mounted() { this.$bus.$on('updateListData', this.handleUpdateListData); }, beforeDestroy() { this.$bus.$off('updateListData', this.handleUpdateListData); }, methods: { handleUpdateListData(dataObj) { console.log('我是 List 组件,收到数据:', this.info.users); this.info = {...this.info,...dataObj}; } } } </script>
<style lang="css" scoped> .album { min-height: 50rem; /* Can be removed; just added for demo purposes */ padding-top: 3rem; padding-bottom: 3rem; background-color: #f7f7f7; }
.card { float: left; width: 33.333%; padding: .75rem; margin-bottom: 2rem; border: 1px solid #efefef; text-align: center; }
.card>img { margin-bottom: .75rem; border-radius: 100px; }
.card-text { font-size: 85%; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <template> <section class="jumbotron"> <h3 class="jumbotron-heading">Search Github Users</h3> <div> <input type="text" placeholder="enter the name you search" v-model="keyword" /> <button @click="searchUsers">Search</button> </div> </section> </template>
<script> /* eslint-disable vue/multi-word-component-names */ import axios from 'axios'; export default { name: 'Test', data() { return { keyword: "" } }, methods: { searchUsers() { this.$bus.$emit('updateListData', {isFirst: true, isLoading: true, errMsg: '', users: []}); axios.get(`https://api.github.com/search/users?q=${this.keyword}`) .then((response) => { console.log(response.data); this.$bus.$emit('updateListData', {isLoading: false, errMsg: '', users: response.data.items}); }) .catch((error) => { console.log(error); this.$bus.$emit('updateListData', {isLoading: false, errMsg: error.message, users: []}); }); } } } </script>
<style></style>
|
101 vue-resource
vue-resource
是 Vue 的一个插件(虽然已年久失修),但是较老的项目可能还会用它。
安装之!
1
| npm install vue-resource
|
在 main.js
中引入并使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import Vue from 'vue'
import App from './App.vue'
import VueResource from 'vue-resource'
Vue.config.productionTip = false
Vue.use(VueResource)
new Vue({ el: '#app', render: h => h(App), beforeCreate() { Vue.prototype.$bus = this }, })
|
在 Search.vue
中将 axios
改为 this.$http
(它们的 API 基本上一样的):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| export default { name: 'Test', data() { return { keyword: "" } }, methods: { searchUsers() { this.$bus.$emit('updateListData', {isFirst: true, isLoading: true, errMsg: '', users: []}); this.$http.get(`https://api.github.com/search/users?q=${this.keyword}`) .then((response) => { console.log(response.data); this.$bus.$emit('updateListData', {isLoading: false, errMsg: '', users: response.data.items}); }) .catch((error) => { console.log(error); this.$bus.$emit('updateListData', {isLoading: false, errMsg: error.message, users: []}); }); } } }
|
102 默认插槽
Note
插槽(Slot)提供了一种将父组件的内容传递到子组件中特定位置的机制。它可以让组件的模板更加灵活和可定制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <template> <div class="container"> <Category :listData="foods" title="美食"> <img src="https://tse2-mm.cn.bing.net/th/id/OIP-C.L4qDFzWpNukuYVDODeUJ9QHaE9?rs=1&pid=ImgDetMain" alt="food"> </Category> <Category :listData="games" title="游戏"> <ul> <li v-for="(g, index) in games" :key="index">{{ g }}</li> </ul> </Category> <Category :listData="films" title="电影"> <video controls src="https://www.w3schools.com/html/mov_bbb.mp4"></video> </Category> </div> </template>
<script> import Category from './components/Category.vue'; export default { name: 'App', components: { Category }, data() { return { foods: ["火锅", "烧烤", "小龙虾", "海鲜"], games: ["王者荣耀", "绝地求生", "英雄联盟", "刺激战场"], films: ["《速度与激情3》", "《蝙蝠侠:黑暗骑士》", "《盗梦空间》", "《泰坦尼克号》"] } }, } </script>
<style lang="css"> .container { display: flex; justify-content: space-around; }
h3 { text-align: center; background-color: orange; }
img, video { max-width: 100%; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <div class="category"> <h3>{{title}}</h3> <slot>默认值。</slot> </div> </template>
<script> /* eslint-disable vue/multi-word-component-names */ export default { name: 'Category', props: ['title'] } </script>
<style lang="css" scoped> .category { background-color: skyblue; width: 200px; height: 300px; } </style>
|
103 具名插槽
当需要多个插槽时,可以给插槽命名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| <template> <div class="container"> <Category :listData="foods" title="美食"> <img slot="center" src="https://tse2-mm.cn.bing.net/th/id/OIP-C.L4qDFzWpNukuYVDODeUJ9QHaE9?rs=1&pid=ImgDetMain" alt="food"> <a slot="footer" href="www.baidu.com">更多</a> </Category> <Category :listData="games" title="游戏"> <ul slot="center"> <li v-for="(g, index) in games" :key="index">{{ g }}</li> </ul> <div slot="footer" class="foot"> <a href="www.baidu.com">单机游戏</a> <a href="www.baidu.com">网络游戏</a> </div> </Category> <Category :listData="films" title="电影"> <video slot="center" controls src="https://www.w3schools.com/html/mov_bbb.mp4"></video> <template v-slot:footer> <div class="foot"> <a href="www.baidu.com">热门</a> <a href="www.baidu.com">经典</a> <a href="www.baidu.com">推荐</a> </div> <h4>欢迎前来观影</h4> </template> </Category> </div> </template>
<script> import Category from './components/Category.vue'; export default { name: 'App', components: { Category }, data() { return { foods: ["火锅", "烧烤", "小龙虾", "海鲜"], games: ["王者荣耀", "绝地求生", "英雄联盟", "刺激战场"], films: ["《速度与激情3》", "《蝙蝠侠:黑暗骑士》", "《盗梦空间》", "《泰坦尼克号》"] } }, } </script>
<style lang="css"> .container, .foot { display: flex; justify-content: space-around; }
h3 { text-align: center; background-color: orange; }
h4 { text-align: center; }
img, video { max-width: 100%; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <div class="category"> <h3>{{title}}</h3> <slot name="center">默认值。</slot> <slot name="footer">默认值。</slot> </div> </template>
<script> /* eslint-disable vue/multi-word-component-names */ export default { name: 'Category', props: ['title'] } </script>
<style lang="css" scoped> .category { background-color: skyblue; width: 200px; height: 300px; } </style>
|
104 作用域插槽
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <template> <div class="container"> <Category :listData="games" title="游戏"> <template scope="{games}"> <ul> <li v-for="(g, index) in games" :key="index">{{ g }}</li> </ul> </template> </Category> <Category :listData="games" title="游戏"> <template scope="{games}"> <ol> <li v-for="(g, index) in games" :key="index">{{ g }}</li> </ol> </template> </Category> <Category :listData="games" title="游戏"> <template slot-scope="{games}"> <ul> <li v-for="(g, index) in games" :key="index">{{ g }}</li> </ul> </template> </Category> </div> </template>
<script> import Category from './components/Category.vue'; export default { name: 'App', components: { Category }, } </script>
<style lang="css"> .container, .foot { display: flex; justify-content: space-around; }
h3 { text-align: center; background-color: orange; }
h4 { text-align: center; }
img, video { max-width: 100%; } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <template> <div class="category"> <h3>{{title}}</h3> <slot :games="games">默认值。</slot> </div> </template>
<script> /* eslint-disable vue/multi-word-component-names */ export default { name: 'Category', props: ['title'], data() { return { games: ["王者荣耀", "绝地求生", "英雄联盟", "刺激战场"], } }, } </script>
<style lang="css" scoped> .category { background-color: skyblue; width: 200px; height: 300px; } </style>
|